if ( typeof(window['n2j7']) == 'undefined' ) {
	window['n2j7'] = function(){}
}

/**
 * n2j7 Select
 */
n2j7.select = function(){}

/**
 * Dropdown создается динамически из списка
 * в data('n2j7_select_object') кладем this 
 * 
 * options.inputArea - область ввода или "кликабельная часть для активации dropdown"
 * options.list - данные для списка
 * options.listElementTemplate - функция формирования "элемента" списка
 */
n2j7.select.prototype.init = function(options){
	/**
	 * Имена используемых css классов
	 */
	this.cssClasses = {
		popupDiv:           'n2j7_smart_select_popup',
		inputArrow:         'n2j7_smart_select_drop_down_arrow',
		inputArrowOpened:   'n2j7_smart_select_drop_down_arrow_opened',
		inputArrowHovered:  'n2j7_smart_select_drop_down_arrow_hovered',
		listItemHovered:    'hovered',
		listItemSelected:   'selected',
		inputLabel:         'n2j7_smart_select_selected_label',
		ifPopupScrolled:    'scrolled',
		scrollDiv:          'scroll',
		scrollTopArrow:     'scrollTopArrow',
		scrollBottomArrow:  'scrollBottomArrow',
		scrollBody:         'scrollBody',
		scrollSlider:       'scrollSlider',
		scrollSliderTop:    'scrollSliderTop',
		scrollSliderBottom: 'scrollSliderBottom',
		scrollBody:         'scrollBody'
	};
	
	this.listHeight = 10;
	/**
	 * Всегда должен быть ссылкой на dom элемент 
	 */
	this.inputArea = $(options.inputArea);
	
	/**
	 * Всегда массив объектов со свойствами value,label + "additional" fields
	 */
	this.data = options.list;
	
	/**
	 * В элементе всегда лежит ссылка на экземпляр класса
	 */
	this.inputArea.data('n2j7_select_object', this);
	
	/**
	 * Задержка при прокручивании списка
	 */
	this.timerDelay = 50;
	
	/**
	 * Множитель задержки при первом прокручивании списка
	 */
	this.timerFirstDelayMultiplier = 5;
	
	/**
	 * Ссылка на DOM выбранного элемента
	 */
	this.selectedItem = null;
	
	/**
	 * Индекс выбранного элемента
	 */
	this.selectedIndex = null;
	
	/**
	 * Функция "формирования" элемента списка (лучше переопределять ее по необходимости в "экземпляре")
	 */
	//this.listElementTemplate = typeof(options.listElementTemplate) == 'function' ? options.listElementTemplate : this.listElementTemplate; 
	 
	/**
	 * Создаем DropDown
	 */
	this.createDropDown();
	
	/**
	 * Вешаем Event Listener - ы
	 */
	this.addListeners();
}

n2j7.select.prototype.createDropDown = function(){
	var parents = this.inputArea.parents();
	
	/**
	 * Среди родителей ищем position=relative элемент, относительно него считается offset 
	 * соответственно относительно него будет позиционироваться наш dropdown
	 */
	var dropDownParent = null;
	parents.each(function(){
		var p = $(this); 
		if (p.css('position') == 'relative' || p.is('body')) {
            dropDownParent = p;
        }
	});
	
	/**
	 * Формируем html для dropdown 
	 */
	var selectedItemText = this.inputArea.find('.' + this.cssClasses.inputLabel).text();
	var listHTML = [this.popupTemplateHeader()];
	for (var item in this.data) {
		/**
		 * Искать по значению (value) а не по тексту (label) если value задано
		 */
		var selected = (selectedItemText == this.data[item].label);
		if(selected){
			this.selectedIndex = item;
		}
		listHTML.push(this.listElementTemplate(this.data[item], selected));
	}
	listHTML.push(this.popupTemplateFooter());
	
	/**
	 * Создаем dropdown, добавляем его в DOM дерево 
	 */
	this.popup = $(listHTML.join(''));
	dropDownParent.append(this.popup);
	
	this.selectedIndex = this.selectedIndex == null ? 0 : this.selectedIndex;
	this.selectedItem = this.popup.find('li:eq('+this.selectedIndex+')');
	this.selectedItem.addClass(this.cssClasses.listItemSelected);
	
	if(this.listHeight && this.data.length > this.listHeight){
		this.createScroll();
	}
	
	/**
	 * Создаем в элементе ссылку на экземпляр класса
	 */
	this.popup.data('n2j7_select_object',this);
	
	/**
	 * Позиционируем popup
	 */
	var offset = this.inputArea.offset();
	this.popup.css({
        top: parseInt(offset.top) + parseInt(n2j7.tools.calculateElementHeight(this.inputArea)), 
        left: parseInt(offset.left),
        width: parseInt(n2j7.tools.calculateElementWidth(this.inputArea))
    });
}

n2j7.select.prototype.addListeners = function(){
    this.addInputListeners();
    this.addDropDownListeners();
}

n2j7.select.prototype.addInputListeners = function(){
	// prevent IE selection
    this.inputArea.get(0).onselectstart = function() { return false; }; 
    this.inputArea.bind('mousedown', {obj:this}, this.onMouseDownInput);
    this.inputArea.bind('mouseover', {obj:this}, this.onMouseOverInput);
    this.inputArea.bind('mouseout', {obj:this}, this.onMouseOutInput);
}

n2j7.select.prototype.onMouseDownInput = function(event){
    if(parseInt(event.button)>=2) return true; // if no left mouse button
	var veto = {};
	var obj = event.data.obj;
	var inputArea = obj.inputArea;
	inputArea.trigger(jQuery.Event('input:click',{obj: obj}),[veto]);
	if(!veto.veto) {
        event.preventDefault ? event.preventDefault() : event.returnValue = false;
    
        inputArea.find('.' + obj.cssClasses.inputArrow).addClass(obj.cssClasses.inputArrowOpened);
        inputArea = null; obj = null;
        return false;
	}
	inputArea = null; obj = null;
	if(typeof veto.returnVal != 'undefined') return veto.returnVal;
}

n2j7.select.prototype.onMouseOverInput = function(event){
	var obj = event.data.obj;
	var veto = {};
	obj.inputArea.trigger(jQuery.Event('input:over',{obj: obj}),[veto]);
	if(!veto.veto) {
        obj.inputArea.find('.' + obj.cssClasses.inputArrow).addClass(obj.cssClasses.inputArrowHovered);
	}
	obj = null;
    if(typeof veto.returnVal != 'undefined') return veto.returnVal;
}

n2j7.select.prototype.onMouseOutInput = function(event){
	var obj = event.data.obj;
	var veto = {};
	obj.inputArea.trigger(jQuery.Event('input:out',{obj: obj}),[veto]);
	if(!veto.veto) {
        obj.inputArea.find('.' + obj.cssClasses.inputArrow).removeClass(obj.cssClasses.inputArrowHovered);
	}
	obj = null;
	if(typeof veto.returnVal != 'undefined') return veto.returnVal;
}

n2j7.select.prototype.addDropDownListeners = function(){
	// открываем dropdown при клике на input
	this.inputArea.bind('input:click',function(event,veto){
		event.obj.popup.toggle();
        event.obj.bindOutEvents();
        event.obj.addKeyboardListeners();
        event.obj.selectElement(event.obj.selectedItem, true);
        return false;
	});
	var listItems = this.popup.find('ul li');
	listItems.bind('mouseover', {obj:this}, this.onMouseOverItem);
	listItems.bind('mouseup', {obj:this}, this.onItemClick);
	/**
	 * при клике по элементу списка остановим выполнение события document.mousedown
	 * которое(по умолчанию) скрывает dropdown
	 */
	listItems.mousedown(function(e){e.stopPropagation();});
}

n2j7.select.prototype.bindOutEvents = function(){
    $(document)
	    .bind('mousedown.n2j7_smart_select',{obj:this},function(event){
	    	event.data.obj.hidePopUp();
	    }) 
	    .bind('mousewheel.n2j7_smart_select',{obj:this},function(event){
	    	event.data.obj.hidePopUp();
	    })
    ; 
}

n2j7.select.prototype.onMouseOverItem = function(event){
    var obj = event.data.obj;
    var veto = {};
    obj.inputArea.trigger(jQuery.Event('item:over',{obj: obj}),[veto]);
    if(!veto.veto) {
        obj.highlightElement($(this));
    }
	obj = null;
	if(typeof veto.returnVal != 'undefined') return veto.returnVal;
}

n2j7.select.prototype.highlightElement = function(listElement) {
	this.popup.find('li').removeClass([this.cssClasses.listItemHovered, this.cssClasses.listItemSelected].join(' '));
    listElement.addClass(this.cssClasses.listItemHovered);
}

n2j7.select.prototype.onMouseOutItem = function(event){
	var obj = event.data.obj;
	var veto = {};
    obj.inputArea.trigger(jQuery.Event('item:out',{obj: obj}),[veto]);
    if(!veto.veto) {
        $(this).removeClass(obj.cssClasses.listItemHovered);
	}
    obj = null;
    if(typeof veto.returnVal != 'undefined') return veto.returnVal;
}

n2j7.select.prototype.onItemClick = function(event){
	if(parseInt(event.button) >= 2) return true;
	var t = $(this);
	var obj = event.data.obj; 
	var veto = {};
    obj.inputArea.trigger(jQuery.Event('item:select',{obj: obj}),[veto]);
    if(!veto.veto) {
        obj.selectElement(t);
    }
    t = null; obj = null;
    if(typeof veto.returnVal != 'undefined') return veto.returnVal;
}

n2j7.select.prototype.selectElement = function(listElement, notCloseAfterSelect){
	this.popup.find('li').removeClass([this.cssClasses.listItemHovered, this.cssClasses.listItemSelected].join(' '));
	listElement.addClass(this.cssClasses.listItemSelected);
    this.inputArea.find('.' + this.cssClasses.inputLabel).text(listElement.text());
    
    var list = this.popup.find('li');
    this.selectedIndex = list.index(listElement);
    this.selectedItem = listElement
    if(!notCloseAfterSelect){
        this.hidePopUp();
    }
    else{
    	this.scrollToSelectedElement();
    }
    list = null;
}

n2j7.select.prototype.listElementTemplate = function(data, isSelected){
	// div into li needed due to IE6
    return [
        '<li value="',data.value,'" class="',(isSelected)?this.cssClasses.listItemSelected:'',
        '"><div>',data.label,'</div></li>'
    ].join('');
}

n2j7.select.prototype.popupTemplateHeader = function(){
    return ['<div class="',this.cssClasses.popupDiv,'">','<ul>'].join('');
}

n2j7.select.prototype.popupTemplateFooter = function(){
    return ['</ul>','</div>'].join('');
}

n2j7.select.prototype.scrollTemplate = function(){
    return [
        '<div class="',this.cssClasses.scrollDiv,'">',
            '<div class="',this.cssClasses.scrollTopArrow,'"></div>',
            '<div class="',this.cssClasses.scrollBody,'">',
                '<div class="',this.cssClasses.scrollSlider,'">',
                    '<div class="',this.cssClasses.scrollSliderTop,'"></div>',
                    '<div class="',this.cssClasses.scrollSliderBottom,'"></div>',
                '</div>',
            '</div>',
            '<div class="',this.cssClasses.scrollBottomArrow,'"></div>',
        '</div>'].join('');
}

n2j7.select.prototype.createScroll = function(){
	this.popupScroll = $(this.scrollTemplate());
    // set scroll & popup height
    var heights = this.getPopupHeights();
    this.popupScroll.height(heights.popupHeight);
    this.popup.append(this.popupScroll).addClass(this.cssClasses.ifPopupScrolled).height(heights.popupHeight);
    var scrlBodyHeight = heights.popupHeight;
    scrlBodyHeight -= parseFloat(this.popupScroll.find('.' + this.cssClasses.scrollTopArrow).outerHeight());
    scrlBodyHeight -= parseFloat(this.popupScroll.find('.' + this.cssClasses.scrollBottomArrow).outerHeight());
    this.popupScroll.find('.' + this.cssClasses.scrollBody).height(scrlBodyHeight);
    // @TODO: prevent small slider
    var scrlSliderHeight = (this.listHeight/this.data.length)*scrlBodyHeight;
    this.popupScrollSlider = this.popupScroll.find('.' + this.cssClasses.scrollSlider);
    this.popupScrollSlider.height(scrlSliderHeight);
    
    // need for scroll event
    this.scrollingArea = this.popup.find('ul');
    this.scrollingAreaMaxOffset = heights.scrollingAreaHeight - heights.popupHeight;
    this.itemHeight = heights.itemHeight;
    this.sliderMaxOffset = scrlBodyHeight - this.popupScrollSlider.outerHeight(true);
    
    // add scroll events
    this.addScrollEvents();
}

n2j7.select.prototype.getPopupHeights = function(){
	var needHide = false;
	if (this.popup.css('display')=='none'){
		this.popup.css({visibility:'hidden',display:'block'});
		needHide = true;
	}
	var itemHeight = parseFloat(this.popup.find('li').outerHeight());
    var popupHeight = itemHeight*this.listHeight;
    var scrollingAreaHeight = this.popup.find('ul').get(0).scrollHeight
    if(needHide){
    	this.popup.css({visibility:'visible',display:'none'});
    }
    
    return {popupHeight:popupHeight, scrollingAreaHeight: scrollingAreaHeight, itemHeight: itemHeight};
}

n2j7.select.prototype.addScrollEvents = function(){
	this.popupScroll.bind('mousedown',function(){return false;}) // prevent dropdown close by click

	// set slider movable
    this.capturePosition;
    this.sliderTopOffset;
    
    this.popupScrollSlider.get(0).onselectstart = function() { return false; }; // IE fix
    this.popupScrollSlider.bind('mousedown',{obj:this},this.onSliderDown);
    
    // set up wheel events
    this.popup.bind('mousewheel',{obj:this},this.onMouseWheel);    
    
    // setup arrows
    this.repeatable = false;
    this.timer = null;
    
    this.popup.find('.' + this.cssClasses.scrollTopArrow)
        .bind('mousedown',{obj:this,direction:1,repeatable:true},this.onSliderStep)
        .bind('mouseup',{obj:this},this.onSliderStepOver)
        .bind('mouseout',{obj:this},this.onSliderStepOver)
    ;
    this.popup.find('.' + this.cssClasses.scrollBottomArrow)
        .bind('mousedown',{obj:this,direction:-1,repeatable:true},this.onSliderStep)
        .bind('mouseup',{obj:this},this.onSliderStepOver)
        .bind('mouseout',{obj:this},this.onSliderStepOver)
    ;
    
    // setup scroll_body click
    this.popup.find('.' + this.cssClasses.scrollBody)
    	.bind('mousedown', {obj:this,repeatable:true}, this.onSliderPageStep)
    	.bind('mouseup', {obj:this}, this.onSliderStepOver)
    	.bind('mouseout',{obj:this},this.onSliderStepOver)
	;
}

n2j7.select.prototype.onSliderPageStep = function(event){
	var obj = event.data.obj;
	// setup
	var clickPointY = event.pageY - $(event.target).offset().top;
	var direction = parseFloat(obj.popupScrollSlider.css('top')) < clickPointY ? -1 : 1;
	var heights = obj.getPopupHeights();
	var offset = heights.popupHeight - heights.itemHeight;
	var sliderHeight = parseFloat(obj.popupScrollSlider.height());
	event.data.clickPoint = clickPointY;
	event.data.direction = direction;
	event.data.offset = offset;
	event.data.sliderHeight = sliderHeight;
	
	// move list
	obj.repeatable = false;
	obj.sliderPageMove(event);
    // start timer move
    obj.repeatable = event.data.repeatable;

    if(obj.repeatable) {
        if (obj.timer)  clearTimeout(obj.timer);
        obj.timer = setTimeout(function() { obj.sliderPageMove(event); },obj.timerDelay*obj.timerFirstDelayMultiplier);
    }
}

n2j7.select.prototype.sliderPageMove = function(event){
	var obj = event.data.obj;
	// move scroll & list
	var newOffsetTop = parseFloat(obj.scrollingArea.css('top')) + event.data.direction * event.data.offset;
	obj.moveList(newOffsetTop);
	var newSliderOffset =  obj.popupScrollSlider.css('top');
	if (newSliderOffset <= event.data.clickPoint && event.data.direction == 1) return false;
	if (newSliderOffset + event.data.sliderHeight >= event.data.clickPoint && event.data.direction == -1) return false;
	
	if(obj.repeatable) {
        if (obj.timer)  clearTimeout(obj.timer);
        obj.timer = setTimeout(function() { obj.sliderPageMove(event); }, obj.timerDelay);
    }
}

n2j7.select.prototype.onSliderDown = function(event){
	var obj = event.data.obj;
    obj.capturePosition = event.pageY;
    obj.sliderTopOffset = parseFloat(obj.popupScrollSlider.css('top'));
    
    $(document).bind('mousemove.n2j7_smart_select_scroll',{obj:obj},function(event){
        var obj = event.data.obj;
        var delta = obj.sliderTopOffset + (event.pageY - obj.capturePosition);
        obj.moveList(-(obj.scrollingAreaMaxOffset * (delta / obj.sliderMaxOffset) ))
    });
    $(document).bind('mouseup',function(){$(document).unbind('.n2j7_smart_select_scroll')});
    return false;
}

n2j7.select.prototype.onMouseWheel = function(event, delta){
	var obj = event.data.obj;
    var newOffsetTop = parseFloat(obj.scrollingArea.css('top')) +  obj.itemHeight * delta;
    obj.moveList(newOffsetTop);
    return false;
}

n2j7.select.prototype.onSliderStep = function(event){
	var obj = event.data.obj;
	// move list
	obj.repeatable = false;
	obj.sliderStepMove(event);
    // start timer move
    obj.repeatable = event.data.repeatable;

    if(obj.repeatable) {
        if (obj.timer)  clearTimeout(obj.timer);
        obj.timer = setTimeout(function() { obj.sliderStepMove(event); },obj.timerDelay*obj.timerFirstDelayMultiplier);
    }
}

n2j7.select.prototype.sliderStepMove = function(event){
	var obj = event.data.obj;
    var newOffsetTop = parseFloat(obj.scrollingArea.css('top')) + obj.itemHeight * event.data.direction;
    obj.moveList(newOffsetTop);
    
    if(obj.repeatable) {
        if (obj.timer)  clearTimeout(obj.timer);
        obj.timer = setTimeout(function() { obj.sliderStepMove(event); },obj.timerDelay);
    }
}

n2j7.select.prototype.onSliderStepOver = function(event){
	var obj = event.data.obj;
    obj.repeatable = false;
    if (obj.timer)  clearTimeout(obj.timer);
}

n2j7.select.prototype.addKeyboardListeners = function(){
	$(document).bind('keydown.n2j7_smart_select',{obj:this},function(event){
		var key = null;
		if(window.event) key=window.event.keyCode;
        else if (event) key=event.which;
        
        //             UNKNOWN      TAB
        if(key==null || key==0 || key==9) return true;
        var obj = event.data.obj;
        // ESC - close popup
        if(key == 27){
        	obj.hidePopUp();
        	return true;
        }
        // ENTER - select highlighted element
        if(key == 13){
        	obj.selectElement(obj.popup.find(['li.',obj.cssClasses.listItemHovered,', li.', obj.cssClasses.listItemSelected].join('')));
        	return true;
        }
        // ARROW DOWN||ARROW RIGHT - highlight next element in the list & select them into input area
        if(key == 40 || key == 39){
        	var list = obj.popup.find('li');
        	var selected = obj.popup.find(['li.',obj.cssClasses.listItemHovered,', li.', obj.cssClasses.listItemSelected].join(''));
            var index = list.index(selected);
            // next element index
            index++;
            index = (index < 0) ? 0 : index;
            index = (index >= list.length) ? list.length-1 : index;
        	obj.selectElement(list.eq(index), true);
        	return true;
        }
        // ARROW UP  || ARROW LEFT - highlight prev element in the list & select them into input area
        if(key == 38 || key == 37){
        	var list = obj.popup.find('li');
            var selected = obj.popup.find(['li.',obj.cssClasses.listItemHovered,', li.', obj.cssClasses.listItemSelected].join(''));
            var index = list.index(selected);
            // prev element index
            index--;
            index = (index < 0) ? 0 : index;
            index = (index >= list.length) ? list.length-1 : index;
            obj.selectElement(list.eq(index), true);
        	return true;
        }
        // PAGE DOWN
        if(key == 34){
            var list = obj.popup.find('li');
            var selected = obj.popup.find(['li.',obj.cssClasses.listItemHovered,', li.', obj.cssClasses.listItemSelected].join(''));
            var index = list.index(selected);
            // next element page
            index += obj.listHeight-1;
            index = (index < 0) ? 0 : index;
            index = (index >= list.length) ? list.length-1 : index;
            obj.selectElement(list.eq(index), true);
            return true;
        }
        // PAGE UP
        if(key == 33){
            var list = obj.popup.find('li');
            var selected = obj.popup.find(['li.',obj.cssClasses.listItemHovered,', li.', obj.cssClasses.listItemSelected].join(''));
            var index = list.index(selected);
            // prev element page
            index -= obj.listHeight-1;
            index = (index < 0) ? 0 : index;
            index = (index >= list.length) ? list.length-1 : index;
            obj.selectElement(list.eq(index), true);
            return true;
        }
        // END
        if(key == 35){
            var list = obj.popup.find('li');
            // final element
            obj.selectElement(list.eq(list.length-1), true);
            return true;
        }
        // HOME
        if(key == 36){
            var list = obj.popup.find('li');
            // first element
            obj.selectElement(list.eq(0), true);
            return true;
        }
        
        // OTHER KEY - search for value(attrs value or option text)
        // if input area is INPUT - put phrase into it
	});
}

n2j7.select.prototype.hidePopUp = function(){
	this.popup.hide();
    $(document).unbind('.n2j7_smart_select');
    this.inputArea.find('.' + this.cssClasses.inputArrow).removeClass(this.cssClasses.inputArrowOpened);
}

n2j7.select.prototype.scrollToSelectedElement = function(){
	// if selected item not on view area - scroll list & synchronize scroll
	// вычислим позицию(верхнюю точку) выбранного элемента
	var selectedItemTopPosition = this.selectedIndex * this.itemHeight;
	var popupHeight = this.itemHeight * this.listHeight;
	var scrollDelta = parseFloat(this.scrollingArea.css('top'));
	scrollDelta = isNaN(scrollDelta) ? 0 : Math.abs(scrollDelta);
	
	// проверим что выбранный элемент находится в видимой зоне
	if(selectedItemTopPosition < scrollDelta || selectedItemTopPosition + this.itemHeight > scrollDelta + popupHeight){
		// за пределами видимой зоны, необходимо "промотать" до элемента
		var newOffsetTop = (selectedItemTopPosition < scrollDelta) ? -selectedItemTopPosition : -(selectedItemTopPosition - popupHeight + this.itemHeight);
		this.moveList(newOffsetTop);
	}
    // в видимой зоне	
}

n2j7.select.prototype.moveList = function(newOffsetTop){    
    newOffsetTop = newOffsetTop > 0 ? 0 : newOffsetTop; // min
    newOffsetTop = newOffsetTop < -this.scrollingAreaMaxOffset ? -this.scrollingAreaMaxOffset : newOffsetTop; // max
    this.scrollingArea.css('top', newOffsetTop);
    var percents = Math.abs(newOffsetTop / this.scrollingAreaMaxOffset);
    var newSliderOffset = this.sliderMaxOffset * percents
    this.popupScrollSlider.css('top', newSliderOffset);
}

/**
 * Tools
 */
n2j7.tools = {};
n2j7.tools.calculateElementWidth = function (obj) {
	return parseFloat(obj.css('padding-left')) + parseFloat(obj.width()) + parseFloat(obj.css('padding-right'));
}
n2j7.tools.calculateElementHeight = function (obj) {
	return parseFloat(obj.css('padding-top')) + parseFloat(obj.height()) + parseFloat(obj.css('padding-bottom'));
}